# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: xguo

import hashlib
import requests
import sys
import time
import urllib.parse

import coin.exchange.base.kr_rest.rest_client_base as rcb
from coin.exchange.base.kr_rest.auth_base import AuthKey
from coin.exchange.util.rate_limit import RateLimiter
from coin.exchange.okex.kr_rest.error_code import okex_strerror


class OkexOrderBase(object):
  def __init__(self, key_file, timeout=5):
    key = AuthKey.from_file(key_file)
    self.access_key = key.access_key
    self.secret_key = key.secret_key
    self.rate_limit = RateLimiter(20, 2.0)
    self.api_url = "https://www.okex.com/api/v1/"
    self.timeout = timeout

  def _get_signature(self, params):
    sorted_params = sorted(params.items(), key=lambda d: d[0], reverse=False)
    encoded_params = urllib.parse.urlencode(sorted_params)
    encoded_params += ('&secret_key=' + self.secret_key)
    return hashlib.md5(encoded_params.encode("utf8")).hexdigest().upper()

  def _query_nocheck(self, endpoint, request_type, params):
    full_url = urllib.parse.urljoin(self.api_url, endpoint)

    req_timestamp = time.time()
    if request_type == 'GET':
      response = requests.get(full_url, params=params, timeout=self.timeout)
    elif request_type == 'POST':
      response = requests.post(full_url, data=params, timeout=self.timeout)
    else:
      raise ValueError('Invalid request type')
    res_timestamp = time.time()
    if response.status_code == 200:
      response_json = response.json()
      if not response_json.get('result', None):
        err_code = response_json['error_code']
        sys.stderr.write("error code: %s\nerror message: %s\n" %
                         (err_code, okex_strerror(err_code)))
      return rcb.RestUpdate(req_timestamp, res_timestamp, response_json)
    else:
      sys.stderr.write("HTTP code: %d\nbody:%s\n" % (response.status_code, response.text))
      raise Exception("Request failed!")

  def _query(self, endpoint, request_type, params=None, disable_rate_limit=False):
    if params:
      params = {key: value for key, value in params.items() if value is not None}
    else:
      params = dict()

    params['api_key'] = self.access_key
    params['sign'] = self._get_signature(params)
    if not disable_rate_limit:
      while not self.rate_limit.check():
        time.sleep(0.1)
    else:
      self.rate_limit.check()
    return self._query_nocheck(endpoint, request_type, params=params)


class OkexNativePrivateClient(OkexOrderBase):
  def __init__(self, key_file):
    OkexOrderBase.__init__(self, key_file, timeout=20)

  # Get User Account Info.
  # Request frequency 6 times/2s.
  def query_POST_userinfo(self):
    url = "userinfo.do"
    update = self._query(url, 'POST')
    return update

  def query_POST_walletinfo(self):
    url = "wallet_info.do"
    update = self._query(url, 'POST')
    return update

  # Place Orders.
  # Request frequency 20 times/2s.
  def query_POST_trade(self, symbol: str, order_side: str, price: float, qty: float):
    if order_side == "bid":
      okex_order_type = 'buy'
    elif order_side == "ask":
      okex_order_type = 'sell'
    else:
      raise ValueError()

    url = "trade.do"
    params = {
        "symbol": symbol, 'price': '%.8f' % price, 'amount': '%.8f' % qty, "type": okex_order_type
    }
    update = self._query(url, "POST", params)
    return update

  # Batch Trade
  # Request frequency 20 times/2s
  def query_POST_batch_trade(self, symbol):
    url = "batch_trade.do"
    raise NotImplementedError()

  # Cancel Orders
  # Request frequency 20 times/2s
  def query_POST_cancel_order(self, symbol: str, order_id):
    url = "cancel_order.do"
    params = {
        "symbol": symbol,
        "order_id": order_id,
    }
    update = self._query(url, "POST", params)
    return update

  # Get Order Info
  # Request frequency 20 times/2s (unfilled orders)
  def query_POST_order_info(self, symbol, order_id=None):
    url = "order_info.do"
    order_id = order_id or -1  # -1 returns all unfilled orders
    params = {
        "symbol": symbol,
        "order_id": order_id,
    }
    update = self._query(url, "POST", params)
    return update

  # Get order information in batch mode.
  # Request frequency 20 times/2s
  def query_POST_orders_info(self, symbol, order_id_list):
    assert type(order_id_list) in (list, tuple)
    url = "orders_info.do"
    params = {
        "symbol": symbol,
        "order_id": ','.join(order_id_list),
    }
    update = self._query(url, 'POST', params)
    return update

  # Only the most recent 2 days are returned
  def query_POST_order_history(self, symbol, status, current_page=0, page_length=200):
    assert status in (0, 1)  # 0: unfilled orders, 1: filled orders
    assert page_length <= 200
    url = "order_history.do"
    params = {
        "symbol": symbol,
        "status": status,
        "current_page": current_page,
        "page_length": page_length,
    }
    update = self._query(url, "POST", params)
    return update

  # BTC/LTC/ETH/ETC/BCH Withdraw
  def query_POST_withdraw(self):
    url = "withdraw.do"
    raise NotImplementedError()

  # Withdrawal Cancellation Request
  def query_POST_cancel_withdraw(self):
    url = "cancel_withdraw.do"
    raise NotImplementedError()

  # Get Withdrawal Information
  def query_POST_withdraw_info(self, symbol):
    url = "withdraw_info.do"
    raise NotImplementedError()

  # Get User Deposits or Withdraw Record
  def query_POST_account_records(self):
    url = "account_records.do"
    raise NotImplementedError()
